package com.sanjin.tinyspring.mvc.api;

import com.sanjin.tinyspring.core.annotation.*;
import lombok.extern.slf4j.Slf4j;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.net.URL;
import java.util.*;

/**
 * @author: sanjin
 * @date: 2019/11/25 22:16
 */
@Slf4j
public class DispatchServlet extends HttpServlet {
    private final String LOCATION_PARAM = "contextConfigLocation";
    private List<String> classNames = new ArrayList<>();
    private Map<String, Object> beanFactory = new HashMap<>();
    private Map<String, Method> handlerMapping = new HashMap<>();
    /**
     * 扫描包路径
     */
    private String scanPackage;
    /**
     * 配置文件
     */
    private Properties locationProperties = new Properties();


    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        try {
            doDispatch(req, resp);
        } catch (Exception e) {
            resp.getWriter().write(Arrays.toString(e.getStackTrace()));
        }
    }

    /**
     * 根绝请求的url，反射执行controller的方法
     * 目前仅支持表单参数的提交,并且参数类型仅限于String
     * <p>
     * //TODO 支持json
     *
     * @param req
     * @param resp
     * @throws Exception
     */
    private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception {
        // req.getRequestURL -> http://www.xx.com/jetty
        // req.getRequestURI -> /jetty
        String uri = req.getRequestURI();

        Method method = handlerMapping.get(uri);
        if (method == null) {
            resp.getWriter().write("404 NOT Found !");
            return;
        }

        // 方法调用的参数
        Map<String, String[]> parameterMap = req.getParameterMap();
        Class<?>[] parameterTypes = method.getParameterTypes();
        Object[] parameterValues = new Object[parameterTypes.length];
        Parameter[] parameters = method.getParameters();

        // 获取方法的参数
        for (int i = 0; i < parameterTypes.length; i++) {
            Class<?> type = parameterTypes[i];
            if (type == HttpServletRequest.class) { // 若方法参数是httpServletRequest

                parameterValues[i] = req;
            } else if (type == HttpServletResponse.class) { // 若方法参数是HttpServletResponse
                parameterValues[i] = resp;
            } else if (type == String.class) { // 参数类型为字符串
                if (parameters[i].isAnnotationPresent(RequestParam.class)) {
                    RequestParam requestParam = parameters[i].getAnnotation(RequestParam.class);
                    String parameterName = requestParam.value();
                    String[] strings = parameterMap.get(parameterName);
                    if (strings != null && strings.length > 0) {
                        parameterValues[i] = strings[0];
                    }
                }
            } else { //TODO 其他类型的参数暂时不支持

            }
        }

        //反射调用方法
        String controllerBeanName = lowerFirstCase(method.getDeclaringClass().getSimpleName());
        Object result = method.invoke(this.beanFactory.get(controllerBeanName), parameterValues);

        // 返回结果
        resp.getWriter().write("<h1>" + result.toString() + "</h1>");
    }

    @Override
    public void init(ServletConfig servletConfig) throws ServletException {
        log.info("DispatchServlet init start =======================");
        //1.加载配置文件
        String contextConfigLocation = servletConfig.getInitParameter(LOCATION_PARAM);
        doLoadConfig(contextConfigLocation);
        log.info("加载配置文件完成");

        //2.扫描包
        doScanPackage(locationProperties.getProperty("scanPackage"));
        log.info("扫描包完成");

        //3.实例化bean
        doInstanceBean();
        log.info("实例化bean完成");

        //4.注入bean的属性
        doAutowire();
        log.info("属性注入完成");

        //5.初始化handlerMapping
        doInitHandlerMapping();
        log.info("初始化handlerMapping完成");

        //6.等待请求，路由到相应的controller


        //7.初始化完成
        log.info("DispatchServlet init end =======================");

    }

    /**
     * 初始化handlerMapping
     */
    private void doInitHandlerMapping() {
        for (Map.Entry<String, Object> entry : beanFactory.entrySet()) {
            Object bean = entry.getValue();
            if (bean.getClass().isAnnotationPresent(Controller.class)) {
                Controller controller = bean.getClass().getAnnotation(Controller.class);
                // controller的类上标识的url
                String baseUrl = controller.value();
                Method[] methods = bean.getClass().getMethods();
                for (Method method : methods) {
                    // 查询controller中方法上的url
                    if (method.isAnnotationPresent(RequestMapping.class)) {
                        RequestMapping requestMapping = method.getAnnotation(RequestMapping.class);
                        String subPath = requestMapping.value();
                        // 拼接url "/" + baseUrl + "/" + subPath ,若有多个"/"相连，转为一个"/"
                        String url = ("/" + baseUrl + "/" + subPath).replaceAll("/+", "/");
                        handlerMapping.put(url, method);
                    }
                }
            }
        }
    }

    /**
     * 注入bean的属性
     */
    private void doAutowire() {
        for (Map.Entry<String, Object> entry : beanFactory.entrySet()) {
            //获取所有的字段
            Field[] fields = entry.getValue().getClass().getDeclaredFields();
            for (Field field : fields) {
                //若字段含有TAutowired注解，则进行属性注入
                if (field.isAnnotationPresent(Autowired.class)) {
                    //TAutowired autowired = field.getAnnotation(TAutowired.class);
                    String fieldAllClassName = field.getType().getName();
                    //设置私有属性访问权限
                    field.setAccessible(true);
                    try {
                        field.set(entry.getValue(), beanFactory.get(fieldAllClassName));
                    } catch (IllegalAccessException e) {
                        log.error("属性注入失败, 待注入类: {},待注入属性: {}",
                                entry.getValue().getClass().getName(),
                                field.getType().getSimpleName());
                        //程序退出
                        System.exit(0);
                    }
                }

            }
        }
    }

    /**
     * 将classNames中标记有目标注解的类进行实例化,添加进容器中
     * 目标注解 TController TService
     * beanName 规则:
     * 1.默认是类名首字母小写
     * 2.TService注解可以指定beanName
     */
    private void doInstanceBean() {
        try {
            for (String className : classNames) {
                Class<?> clazz = Class.forName(className);
                //若bean有TController注解
                if (clazz.isAnnotationPresent(Controller.class)) {
                    //getSimpleName是类名，getName是全类名
                    String beanName = lowerFirstCase(clazz.getSimpleName());
                    beanFactory.put(beanName, clazz.newInstance());
                }
                //
                if (clazz.isAnnotationPresent(Service.class)) {
                    //尝试从注解中取出beanName
                    Service service = clazz.getAnnotation(Service.class);
                    String beanName = service.value();
                    //若设置了beanName，使用该beanName
                    if (!beanName.equals("")) {
                        beanFactory.put(beanName, clazz.newInstance());
                        continue;
                    }
                    // 若没有设置beanName，使用接口的名字做为beanName
                    Class<?>[] interfaces = clazz.getInterfaces();
                    for (Class in : interfaces) {
                        //为方便属性注入，service 的beanName设置为接口全类名
                        beanFactory.put(in.getName(), clazz.newInstance());
                    }
                } else {
                    // 没有注解的beanName不进行处理
                }
            }
        } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
            e.printStackTrace();
        }
    }

    /**
     * 将字符串首字母小写，用于生成beanName
     * 如输入 DemoService，输出demoService
     *
     * @param str
     * @return
     */
    private String lowerFirstCase(String str) {
        char[] chars = str.toCharArray();
        chars[0] += 32;
        return String.valueOf(chars);
    }

    /**
     * 获取包名下所有类的全类名，方便后面反射实例化对象
     *
     * @param scanPackage 包名 如com.sanjin.tinyspring.mvc.test
     */
    private void doScanPackage(String scanPackage) {
        // com.sanjin.tinyspring.mvc.test -> /com.sanjin/tinyspring/mvc/test

        // 获取class目录
        URL url = this.getClass().getClassLoader().getResource("/" + scanPackage.replaceAll("\\.", "/"));
        if (url == null) throw new RuntimeException("can find the scaned package path");
        File file = new File(url.getFile());
        File[] files = file.listFiles();
        if (files != null) {
            for (File f : files) {
                if (f.isDirectory()) {
                    // 目录递归处理
                    doScanPackage(scanPackage + "." + f.getName());
                } else {
                    // 文件若为.class结尾，将类的全类名添加进容器
                    if (f.getName().endsWith(".class")) {
                        classNames.add(scanPackage + "." + f.getName().replace(".class", "").trim());
                    }
                }
            }
        }

    }

    /**
     * @param contextConfigLocation spring的配置文件名称，如 application.properties
     */
    private void doLoadConfig(String contextConfigLocation) {
        //从classpath中读取properties配置文件
        InputStream is = null;
        try {
            // 读取 properties
            is = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation);
            locationProperties.load(is);
        } catch (Exception e) {
            log.info("load application.properties file failure...");
            e.printStackTrace();
        } finally {
            if (is != null) {
                try {
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
